iT邦幫忙

2022 iThome 鐵人賽

DAY 8
0
Software Development

30天成為鍵盤麥可貝:前端視覺特效開發實戰系列 第 8

Day8: Three.js 你有被光速踢過嗎?解析3D界的黃猿——光的底層原理與介紹

  • 分享至 

  • xImage
  •  

three.js的光

https://ithelp.ithome.com.tw/upload/images/20220923/20142505lBQCIkrPtD.png

圖片來源

有看過航海王的人,應該對這句話非常有印象:「你有被光速踢過嗎?」。

身為海軍三大將的黃猿,最能利用光速打敗對手。跟我們的視覺特效一樣,很多都敗給了光——尤其在處理shader的時候。

開發視覺特效的我們,光暈、光的反射、鏡面等等都需要光。即使在比較高階的three.js裡面開發光,無論是DirectionalLight、AmbientLight、RectAreaLight、HemosphereLight、PointLight都一樣,只要是照到Mesh的,都離不開光。及使用WebGL開發也是。以下介紹:

最常用的光源及其原理

在three.js裡面,光分成很多種,包含:

AmbientLight:環境光

物體的每一個面都吸到一樣的光。其實就是很像直接調整物件的曝光度,簡單粗暴。

AmbientLight, three.js, webGL, light

// 新增環境光
const light = new THREE.AmbientLight(0xffffff,1)
scene.add(light)

AmbientLight, three.js, webGL, light

DirectionalLight:平行光

由一個方向發光。對所有面來說,光的方向都是一樣的

DirectionalLight, three.js, webGL, light

// 新增平行光
const directionalLight = new THREE.DirectionalLight(0xffffff,0.4)
scene.add(directionalLight);
// 新增Helper
const lightHelper = new THREE.DirectionalLightHelper(directionalLight, 20, 0xffff00)
scene.add(lightHelper);
// 更新光源位置
directionalLight.target.position.set(0,0,0);
directionalLight.target.updateMatrixWorld();
// 更新Helper
lightHelper.update();

DirectionalLight, three.js, webGL, light

PointLight:點光

由一個點發光。對所有面來說,光的方向是不一樣的

PointLight, three.js, webGL, light

// 新增點光
const pointLight = new THREE.PointLight(0xffffff, 0.4)
scene.add(pointLight);
// 更新光源位置
pointLight.position.set(3,3,0)
// 新增Helper
const lightHelper = new THREE.PointLightHelper(pointLight, 20, 0xffff00)
// 更新Helper
lightHelper.update();

PointLight, three.js, webGL, light

RectAreaLight:區域光

由一個面發光。對於被投影到的面來說,光的方向是一樣的。對沒被投影到的面來說,光的方向是不一樣的。另外,這是不支援陰影的光。

RectAreaLight, three.js, webGL, light

// 區域光
const rectLight = new THREE.RectAreaLight( 0xffffff, 0.2,  10, 10 );
scene.add( rectLight )
// 更新光源位置
rectLight.position.set( 5, 5, 0 );
// 新增Helper
const rectLightHelper = new RectAreaLightHelper( rectLight );
rectLight.add( rectLightHelper );

RectAreaLight, three.js, webGL, light

不得不說,把區域光放在場景裡真的很漂亮

RectAreaLight, three.js, webGL, light

HemisphereLight:半圓球的光

光從上下發出。等同天空與地面發出的光。

「為什麼叫半圓球?只是兩方向平行光而已」

HemisphereLight, three.js, webGL, light

// 半球光
const hemisphereLight = new THREE.HemisphereLight( 0xffff99, 0x9999ff, 0.2 );
scene.add( hemisphereLight );

上方是黃色,下方是藍色。黃色可以照到地面,使得場景很像黃昏,藍色又可以照到天球,使得天空還是藍色。

HemisphereLight, three.js, webGL, light

這些光怎麼來的?

three.js提供這些光,那麼WebGL會有這些光嗎?

如果有的話,這些光子是怎麼投射到物體,然後反射到鏡頭的?

反射到鏡頭之後,又是怎麼投影到多麼多顆像素上呢?

這邊一一解釋:

dot product, three.js, webGL, light

光就是內積算出的結果

內積的結果:平行光

你現在有一顆球。

dot product,directional light, three.js, webGL, light

假設這球解析度很低,所以這顆球法線長這樣:

dot product, normal, directional light, three.js, webGL, light

然後有一道光,照在球上面:

dot product, normal, directional light, three.js, webGL, light

換句話說,每一個面都指向光源:

dot product, normal, directional light, three.js, webGL, light

光也是一個向量,為了方便計算,我們也給它一個單位向量(長度為一單位的向量),而法線也是:

dot product, normal, unit vector, directional light, three.js, webGL, light

有了這兩個單位向量,就可以透過內積計算這兩條有多相似。內積要怎麼計算兩個單位向量?可以參考上一篇文章


光的強度 = 法線的單位向量.向光的單位向量
光的強度 = |法線|*|向光|*cos(θ)

由此,我們可以得知每一個法線有多麼面向光,用一個數值表示。

dot product, normal, unit vector, directional light, three.js, webGL, light

你看面向光的那一面,其法線單位向量等同於光,所以最亮,經過內積計算唯一。斜面次之,垂直的面則為零。

而這個數值,就可以成為每一個面有多亮的依據了。

Three.js 以我們常用的光,來實作上面這一段。如此一來,你只要調整光,就不用我們親手處理內積。

dot product, normal, unit vector, directional light, three.js, webGL, light

要怎麼求出法線?它存在的地方:Normal

上面所提及的法線是跟面垂直的線。而3D的世界裡,法線應該要存在哪裡?

法線要由面構成,面是由三角面構成,三角面是透過三個錨點所構成。三個錨點是最原始的資料!因此,我們把「法線」的資料儲存在錨點裡面,於是我們就有了Normal。

「那個向量就叫normal」

normal, directional light, three.js, webGL, light

圖片來源

事實上,不只three.js,隔壁棚的babylon,連3Ds Max、Maya的Arnold,跟非常多3D渲染的工具都如此。

內積的結果:點光

平行光是這樣的:拿法線跟光的方向來計算內積。那點光呢?點光的概念跟平行光相同,但多了點東西,那就是:在點光中,每個面所朝向的光,其方向都不同。

normal, point light, three.js, webGL, light

轉成單位向量之後,即可以看出變化:在平行光時,球體的頭頂的腳底,都是垂直,也就是90度,得出來就是0;可是在點光時,還不到球的頭頂,就已經超過90度了。

normal, point light, three.js, webGL, light

而這樣的角度也意味其內積的結果,如圖:
你如果比較前面所提到的平行光,你會發現:原本朝上跟朝下的面跟光源垂直90度(計算結果為0),但在這裡則垂直超過90度,計算結果為負數,也就是說光照射的範圍更小了。

normal, point light, three.js, webGL, light

聚光燈

讓我們來想想聚光燈是怎麼產生的。

normal, spot light, three.js, webGL, light

其實就是具有遮罩的點光。只要傳入參數,使得聚光可以修改可接受的內積範圍,即可控制大小。

normal, spot light, three.js, webGL, light

重新整理一次

  • DirectionalLight
    • 也就是平行光,光打在每一個面的方向是一樣的。
  • PointLight
    • 就是點光,光打在每一個面的方向是輻射狀的。
  • SpotLight
    • 就是被限制光源角度的點光。
  • AmbientLight
    • 直接調整物件本身顏色的亮度。沒有陰影
  • RectAreaLight
    • 非常奇妙,下篇解釋。沒有陰影

以上three.js都幫你包好了

那為什麼我們需要知道?因為在後期,當我們要實作「內光暈」、「外光暈」或是一些特殊的特效時,我們仍需要透過底層的邏輯來實現,身為開發網頁視覺特效的我們不得不知道。而「光」是實作特效的最佳範例,它可是模範生呢!

以上都是比較底層的東西,其實也就是WebGL的計算過程。在WebGL裡面,我們要自己創作光源,得寫一個函式去計算光跟物體normal的關係。

但到了three.js,已經透過WebGL處理掉了光源的製作,並且提供了幾個物件,使我們快速導入光。

雖然人家都包好了,但我們還是得知道以上原理,身為做視覺特效的我們得學會!因為在後面的Shader,我們就得靠自己,不能靠three.js了,不過沒關係,我到時候也會帶大家介紹一遍。

既然知道原理

下一篇將介紹實作。我們知道光的原理,勢必得來實作看看。

除此之外,我也將介紹 RectAreaLight 的原理。


上一篇
Day7: three.js的一方通行:矢量操作——全面釐清向量與底層特性
下一篇
Day9: Three.js 傲慢的太陽——光的開發與矩形區域光原理
系列文
30天成為鍵盤麥可貝:前端視覺特效開發實戰31
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言